package com.useful.core.component;

import cn.hutool.core.io.IoUtil;
import com.jcraft.jsch.*;
import com.useful.core.constant.HeadersConstant;
import com.useful.core.exception.BizException;
import com.useful.core.http.CustomerMediaType;
import com.useful.core.pool.StringPool;
import com.useful.core.prop.FtpProperties;
import com.useful.core.utils.Func;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

/**
 * @author : wenchao.long
 * @date : Created in 2021/9/20 21:30 周一
 * @description :
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class FtpComponent {

    private final FtpProperties properties;

    /**
     * 上传文件
     */
    public void uploadFile(MultipartFile file) {
        String filename = file.getOriginalFilename();
        InputStream is = null;
        ChannelSftp sftp = getConnectSftp();
        try {
            is = file.getInputStream();
            sftp.cd(properties.getBasePath());
            sftp.put(is, filename);
        } catch (SftpException | IOException e) {
            throw new BizException("上传文件失败");
        } finally {
            if (is != null) {
                IoUtil.close(is);
            }
            if (sftp != null) {
                close(sftp);
            }
        }
    }

    /**
     * 下载文件
     */
    public void download(String fileName, HttpServletResponse response) {
        ChannelSftp sftp = getConnectSftp();
        InputStream is = null;
        ServletOutputStream os = null;
        try {
            is = sftp.get(properties.getBasePath() + "/" + fileName);
            os = response.getOutputStream();
            byte[] bytes = IoUtil.readBytes(is);
            response.setHeader(HeadersConstant.CONTENT_TYPE, CustomerMediaType.APPLICATION_ZIP);
            response.setHeader(HeadersConstant.CONTENT_LENGTH, String.valueOf(bytes.length));
            os.write(bytes);
        } catch (SftpException | IOException e) {
            log.info(e.getMessage() + ",文件下载失败: {}", fileName);
            throw new BizException("文件下载失败");
        } finally {
            if (is != null) {
                IoUtil.close(is);
            }
            if (os != null) {
                IoUtil.close(os);
            }
            if (sftp != null) {
                close(sftp);
            }
        }
    }

    /**
     * 下载多文件
     */
    public void download(List<String> fileNames, HttpServletResponse response) {
        String zipFileName = Func.random(4) + properties.getZipSuffix();
        // 压缩文件
        zipFile(fileNames, zipFileName);
        // 下载压缩文件
        download(zipFileName, response);
        // 删除压缩文件
        removeFile(zipFileName);
    }

    /**
     * 删除文件
     */
    public void removeFile(String fileName) {
        ChannelSftp sftp = getConnectSftp();
        try {
            sftp.cd(properties.getBasePath());
            sftp.rm(fileName);
        } catch (SftpException e) {
            log.info(e.getMessage() + ",此文件不存在: {}", fileName);
            throw new BizException("删除文件失败");
        } finally {
            if (sftp != null) {
                close(sftp);
            }
        }
    }

    private void zipFile(List<String> fileNames, String zipFileName) {
        ChannelExec exec = getConnectExec();
        InputStream is = null;
        try {
            String fileName = String.join(" ", fileNames);
            String command = "sh" + StringPool.SPACE + properties.getRemoteShellScript() + StringPool.SPACE +
                    zipFileName + StringPool.SPACE + fileName;
            log.info("执行的命令: {}", command);
            exec.setCommand(command);
            exec.setErrStream(System.err);
            is = exec.getInputStream();
            exec.connect();
            byte[] tmp = new byte[1024];
            while (true) {
                while (is.available() > 0) {
                    int i = is.read(tmp, 0, 1024);
                    if (i < 0) {
                        break;
                    }
                    //执行结果打印到程序控制台
                    System.out.print(new String(tmp, 0, i));
                }
                if (exec.isClosed()) {
                    if (is.available() > 0) {
                        continue;
                    }
                    //获取退出状态，状态0表示脚本被正确执行
                    System.out.println("exit-status: " + exec.getExitStatus());
                    break;
                }
                TimeUnit.SECONDS.sleep(1);
            }
        } catch (JSchException | IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                IoUtil.close(is);
            }
            if (exec != null) {
                close(exec);
            }
        }
    }

    /**
     * 连接远程sftp
     */
    private ChannelSftp getConnectSftp() {
        Session session = getConnect();
        ChannelSftp sftp;
        try {
            sftp = (ChannelSftp) session.openChannel("sftp");
            sftp.connect();
        } catch (JSchException e) {
            throw new BizException("连接服务器失败");
        }
        return sftp;
    }

    /**
     * 远程连接exec
     */
    private ChannelExec getConnectExec() {
        Session session = getConnect();
        ChannelExec exec;
        try {
            exec = (ChannelExec) session.openChannel("exec");
        } catch (JSchException e) {
            throw new BizException("连接服务器失败");
        }
        return exec;
    }

    /**
     * 获取session连接
     */
    private Session getConnect() {
        JSch jSch = new JSch();
        Session session;
        try {
            session = jSch.getSession(properties.getUsername(), properties.getAddress(), properties.getPort());
            session.setPassword(properties.getPassword());
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            session.setConfig(sshConfig);
            session.connect();
        } catch (JSchException e) {
            throw new BizException("连接服务器失败");
        }
        return session;
    }

    /**
     * 关闭连接
     */
    private void close(Channel channel) {
        try {
            Session session = channel.getSession();
            if (session != null) {
                session.disconnect();
            }
            channel.disconnect();
        } catch (JSchException e) {
            e.printStackTrace();
        }
    }
}
